module Main where
import IO
import Char
import Text.ParserCombinators.Parsec
import Data.List
import Data.Maybe


type Formula = String

data CellType  = 	Empty | 
			CellNumber Float | 
			CellString String |
			CellFormula Formula Float
			deriving (Read, Show, Eq)

getVal :: CellType -> String
getVal (Empty) = ""
getVal (CellNumber  a) = show ()
getVal (CellString  a) = a
getVal (CellFormula _ b) = show b

getFormula :: CellType -> String
getFormula (CellFormula a _) = a



evaluate (CellNumber a) tab = a
evaluate (CellString a) tab = 0.0 --evaluate w zaleznosci od operatora
evaluate Empty tab= 0.0
evaluate (CellFormula a b) tab  | (isSum a) = evaluateProduction range tab
                                | (isProduction a) = evaluateProduction range tab
                                | (isAverage a) = evaluateAverage range tab
		where range = getRange a

evaluateProduction range tab = 1.0

evaluateAverage range tab = 3.0




{--
evaluateSum range tab | (length range == 2) =  evaluateSumTableRange min_row min_col max_row max_col tab
		      | otherwise            =  evaluateTableCell min_row min_col tab
                 where min_row = (digitToInt (getFirst (getFirst range) ) )
                       min_col = (digitToInt (getSecond (getFirst range) ) )
		       max_row = (digitToInt (getFirst (getSecond range) ) )
                       max_col = (digitToInt (getSecond (getSecond range) ) )
--}



evaluateRow [] tab = []
evaluateRow (x:xs) tab  = evaluate x tab : evaluateRow xs tab

--evaluateSumTableRange :: Int -> Int -> Int -> Int -> [[CellType]] -> Float
evaluateSumTableRange min_row min_col max_row max_col tab =  sum ( evaluateRow ( flatten ( transpose ( take (max_col- min_col + 1 ) (drop min_col ( transpose ( take (max_row- min_row + 1) ( drop min_row tab ) ) ) ) ) ) )  tab  )

--evaluateTableCell :: Int -> Int -> [[CellType]] -> Float
evaluateTableCell row col tab  =   sum ( evaluateRow ( flatten ( take 1 ( drop col ( transpose ( take 1 ( drop row tab ) ) ) ) ) ) tab )

flatten :: [[a]] -> [a]
flatten [] = []
flatten [[]] = []
flatten ([]:ys) = flatten ys
flatten ((x:xs):ys) = x : (flatten (xs:ys))





--DODAWANIE/USUWANIE WIERSZY I KOLUMN-----------------------------------------------------------------

--DODAWANIE WIERSZY I KOLUMN-----------------------------------------------------------------
--dodaje kolumne na wskazanym miejscu i przesuwa pozostale dane w prawo
addColumn a pos = addCol a (findColNumber pos)

--dodaje wiersz we wskazanym miejscy - przesuwa pozostałe dane w dol
addRow :: [[CellType]] -> Int -> [[CellType]]
addRow a pos = addR a pos (length (head a))

addR :: [[CellType]] ->Int -> Int -> [[CellType]]
addR [] 0 size = [createRow size]
addR [] _ _ = []
addR (x:xs) 0 size = (createRow size):x:xs
addR (x:xs) pos size = x:(addR xs (pos-1) size)

createRow 0 = []
createRow i = (Empty):(createRow (i-1))

addCol :: [[CellType]] -> Int -> [[CellType]]
addCol [] 0 = []
addCol [] _ = []
addCol (x:xs) pos = (addCellInRow x pos):(addCol xs pos)

addCellInRow [] 0 = [Empty]
addCellInRow [] _ = []
addCellInRow (x:xs) 0 = (Empty):x:xs
addCellInRow (x:xs) pos = x:(addCellInRow xs (pos-1))


--USUWANIE WIERSZY I KOLUMN----------------------------------------------------------------
delRow :: [[CellType]] -> Int -> [[CellType]]
delRow [] _ = []
delRow (x:xs) 0 = xs
delRow (x:xs) pos = x:(delRow xs (pos-1))

--Tworzenie nowego wiersza i tabeli-----------------------------------------------------
newRow _ 0 = [] 
newRow value row = value : (newRow value (row-1) )

newTable _ 0 _ = []
newTable value rowsSize colsSize = ( newRow value colsSize ) : ( newTable value (rowsSize - 1) colsSize )

--Ustawianie zadanej wartosci wiersza i tabeli
setRowValue (x:xs) value 0   = value : xs
setRowValue (x:xs) value col = x : setRowValue xs value (col - 1) 

setTableValue (x:xs) value 0 col   =  (setRowValue x value col) : xs
setTableValue (x:xs) value row col =  x : (setTableValue xs value (row - 1) col)

{-
--Dodawanie, usuwanie elementow wiersza
insertIntoRow (x:xs) value 0   = value : (x:xs)
insertIntoRow (x:xs) value col = x : setRowValue (x:xs) value (col - 1) 
-}

{-
removeFromRow 0 (_:xs) = xs 
removeFromRow col (x:xs) = x : (removeFromRow (col - 1) xs)

--Dodawanie usuwanie elementow tabeli
removeColumnFromTable col [] = []
removeColumnFromTable col (x:xs) = (removeFromRow col x) : (removeColumnFromTable col xs)
-}


findColNumber :: String -> Int
findColNumber a = (ord (toLower(head a)))-97


--odzyskanie przechowywanej wartości w komórce-------------------------------------------------------------
getRowValue [] _ = Empty
getRowValue (x:xs) 0 = x
getRowValue (x:xs) col = getRowValue xs (col-1)

getTableValue [] _ _ =  getRowValue [] 0
getTableValue (x:xs) 0 col = getRowValue x col
getTableValue (x:xs) row col = getTableValue xs (row-1) col


--WYPISYWANIE PELNEJ WARTOSCI KOMORKI---------------------------------------------------------------------
--przyjmuje tablicę, pozycję typu "B2" a następnie zwraca stringa z wartością wybranej komórki
showSingle :: [[CellType]] -> Int->Int-> String
showSingle a col row = getVal (getTableValue a row col)


--WYPISYWANIE TABLICY ---------------------------------------------------------------------------------------
--zwraca gotowego stringa z arkusza - trzeba wrzucić to tylko w putStrLn :)
showTable :: [[CellType]] -> String
showTable a = (showCol 65 (length (head a))) ++ "\n" ++ (showTab a 1)

--zwraca linię z literowymi oznaczeniami kolumn w ilości i znaków (początkowo dajemy a=65 -> 'A')
showCol :: Int -> Int -> String
showCol a 0 = ""
showCol a i = "\t" ++ [chr a] ++ (showCol (a+1) (i-1))

showTab :: [[CellType]] -> Int -> String
showTab [] i =  {-(show i) ++-} ""
showTab (a:as) i = (show i) ++ "\t" ++ (showRow a)++"\n"++(showTab as (i+1))

showRow :: [CellType] -> String
showRow [] = ""
showRow (a:as) = (showCell a)++"\t"++(showRow as)

showCell :: CellType -> String
--showCell a = take 7(getVal a)
showCell a = case (length (getVal a)) > 7 of
		True	-> (take 5 (getVal a)) ++ ".."
		False	-> take 7 (getVal a)


--PARSOWANIE PLIKU CSV----------------------------------------------------------------------------------------------
--plik csv zawiera 0 badz wiecej linii, ktore koncza sie znakiem konca linii 'eol'
csvFile :: GenParser Char st [[String]]
csvFile = 
    do result <- many line
       eof
       return result

--kazdy wiersz zawiera jedna badz wiecej komorek oddzielonych srednikami
line :: GenParser Char st [String]
line = 
    do result <- cells
       eol
       return result
       
-- buduje liste komorek.  parsuje pierwsza komorke, a potem zajmuje sie reszta
cells :: GenParser Char st [String]
cells = 
    do first <- cellContent
       next <- remainingCells
       return (first : next)

-- komorka moze konczyc sie srednikiem, co oznacza, ze oczekujemy kolejnej/kolejnych komorek w wierszu,
-- natomiast jesli srednikiem sie nie konczy, oznacza to, ze jestesmy na koncu linii
remainingCells :: GenParser Char st [String]
remainingCells =
    (char ';' >> cells)
    <|> (return [])

-- kazda komorka zawiera 0 lub wiecej znakow, ktore nie moga byc srednikiem ani koncem linii
cellContent :: GenParser Char st String
cellContent = 
    many (noneOf ";\n")
       

-- znakiem konca linii jest \n
eol :: GenParser Char st Char
eol = char '\n'

--PARSUJE STRINGA W FORMACIE CSV DO [[String]]
parseCSV :: String -> Either ParseError [[String]]
parseCSV input = parse csvFile "(unknown)" input

--WYCIĄGANIE TABELI DANYCH PO PARSOWANIU PLIKU
getCSVData (Right b) = b

readTable :: [[String]] -> [[CellType]]
readTable [] = []
readTable [a] = [readRow a]
readTable (a:as) = (readRow a) : (readTable as)

readRow :: [String] -> [CellType]
readRow [] = []
readRow [a] = [readCell a]
readRow (a:as) = (readCell a):(readRow as)

readCell a = read a :: CellType

--zwraca tabele [[CellType]] utworzona z wczytanego pliku
getData :: String -> [[CellType]]
getData a = readTable(getCSVData (parseCSV a))


--ZAPISYWANIE DANYCH-----------------------------------------------------------------------------------------------
--zapisuje do pliku 'fname' dane 'a'
saveToFile a = do
			putStrLn "Podaj nazwe pliku: "
			fname <- getLine
			handle <- openFile fname WriteMode
			hPutStrLn handle (saveTable a)
			putStrLn "Zapisano."
			hClose handle

--przeksztalca tablice danych do Stringa
saveTable :: [[CellType]] -> String
saveTable [] = ""
saveTable [x] = saveRow x
saveTable (x:xs) = (saveRow x) ++ "\n" ++ (saveTable xs)

--przeksztalca wiersz danych do stringa
saveRow :: [CellType] -> String
saveRow [] = ""
saveRow [x] = saveCell x
saveRow (x:xs) = (saveCell x) ++ ";" ++ (saveRow xs)

--przekształca zawartosc komorki do stringa
saveCell :: CellType -> String
saveCell x = show x


--Funkcje sprawdzajace jakiego typu jest string
isInteger s = case reads s :: [(Integer, String)] of
  [(_, "")] -> True
  _         -> False
 
isDouble s = case reads s :: [(Double, String)] of
  [(_, "")] -> True
  _         -> False
 
isNumeric :: String -> Bool
isNumeric s = isInteger s || isDouble s

--isSum = case reads s :: [String] of 









--use late list comprehension/zip or other crappy function to genearete more ids
rowIds = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
--fromJust ( elemIndex s rowIds )


--checkPrefix :: String String -> Bool
checkPrefix formula prefix =
    if restIsJust
      then True
      else False
  where
    (restIsJust, restOfString) =
        case stripPrefix prefix formula of
            Just something -> (True, something)
            Nothing -> (False, undefined)

isSum :: Formula -> Bool
isSum formula = checkPrefix formula "=SUMA"

isProduction :: Formula -> Bool
isProduction formula = checkPrefix formula "=ILOCZYN"

isAverage :: Formula -> Bool
isAverage formula = checkPrefix formula "=SREDNIA"

isFormula :: Formula -> Bool
isFormula formula = (isSum formula) || (isAverage formula) || (isProduction formula)


--some hashell string utils

split :: String -> Char -> [String]
split [] delim = [""]
split (c:cs) delim
   | c == delim = "" : rest
   | otherwise = (c : head rest) : tail rest
   where
       rest = split cs delim

cutFromString :: Eq a => [a] -> [a] -> [a]
cutFromString [] _  = []
cutFromString s find =
    if take (length find) s == find
        then  (cutFromString (drop (length find) s) find )
        else [head s] ++ (cutFromString (tail s) find )


sortTuple (a,b) | a <= b  =  (a, b)
                | otherwise = (b, a)
                           
--sortTuple :: (Integer,Integer) -> (Integer,Integer)
--sortTuple (a,b) | a < b  =  (b, a)
--                | otherwise = (b, a)


-- "C3:B1" --> ((1,1),(2,3))
--getRange :: [t] -> [(t, t)]
--getRange :: String -> Int
--getRange rangeExpr | (length (split rangeExpr ':')) ==  2 = (sortRange (split rangeExpr ':'))
--                   | otherwise = error "Niepoprawny format zakresu"



--splitTokens formula = split formula ';'



cutStrings rangeExpr [] = rangeExpr
cutStrings rangeExpr (x:xs) = cutStrings (cutFromString rangeExpr x) xs

cutFormula rangeExpr = cutStrings rangeExpr ["=SUMA", "=ILOCZYN", "=SREDNIA", ")", "("]



getRange rangeExpr = getProperRange ( (split ( cutFormula rangeExpr ) ':') )




getRowCoordinates [] = []
getRowCoordinates (x:xs) =  (getFirst x) : getRowCoordinates xs

getColCoordinates [] = []
getColCoordinates (x:xs) =  (getSecond x) : getColCoordinates xs

getFirst (x:y) = x

getSecond (x:y) = y



-- ["A5","F1","D3"] -> [("A",1),("F",5)]
getProperRange xs | ( (min_x_cor == max_x_cor) && (min_y_cor == max_y_cor )) =  [(min_x_cor,min_y_cor)]
                  | otherwise =                                                 [(min_x_cor,min_y_cor),(max_x_cor,max_y_cor)]
		where x_cor = getRowCoordinates xs
		      y_cor = getColCoordinates xs	   		
                      min_x_cor = minimum x_cor
                      max_x_cor = maximum x_cor 
                      min_y_cor = minimum y_cor 
                      max_y_cor = maximum y_cor 
                      



-- ["C3","B1"] --> "(B1,C3)"  --> ((1,1),(2,3))
--sortRange :: [String] -> (Int,Int),(Int,Int))
--sortRange ::  [[t]] -> [(t, t)]
--sortRange splitedRangeExpr = map sortTuple (zip (splitedRangeExpr !! 0)  (splitedRangeExpr !! 1))                      





--KOMUNIKACJA Z UZYTKOWNIKIEM---------------------------------------------------------------------------------------
main = do
	putStrLn "Witaj w programie! Wybierz co chcesz zrobic:"
	putStrLn " a) Stworz nowy arkusz kalkulacyjny"
	putStrLn " b) Otworz istniejacy arkusz kalkulacyjny"
	putStrLn " q) Wyjdz z programu"
	putStrLn "?:> "
	chose <- getLine
	submenu chose

submenu x
	| x=="a"	= createSpreadsheet
	| x=="b"	= openSpreadsheet
	| x/="q"	= do
				putStrLn "nieznana funkcja !!!"
				main
	| otherwise	= putStrLn "Dziekuje, zapraszam ponownie!"

createSpreadsheet = do
		let tablica = newTable Empty 2 2
		putStrLn (showTable tablica)
		putStrLn "Utworzono now arkusz.\n"
		actions tablica

openSpreadsheet =	catch (do
				putStrLn "Podaj nazwe pliku do odczytu:"
				fname <- getLine
				handle <- openFile fname ReadMode
				contents <- hGetContents handle
				let tablica = getData contents
				putStrLn (showTable tablica)
				putStrLn "\nPlik odczytany."
				hClose handle
				actions tablica
			) errorHandler 
			where
				errorHandler e = if isDoesNotExistError e	then do
											putStrLn "nie ma takiego pliku"
											main
										else do 
											putStrLn "nie udalo sie wczytac pliku :("
											main

actions tablica = do
		putStrLn "Co chcesz zrobic:"
		putStrLn " a) Zapisz otwarty arkusz kalkulacyjny"
		putStrLn " b) Wyświetl zawartość otwartego arkusza"
		putStrLn " c) Wyswietl pelna zawartosc wybranej komorki"
		putStrLn " d) Dodaj pusty wiersz"
		putStrLn " e) Dodaj pusta kolumne"
		putStrLn " f) Modyfikuj zawartosc komorki"
		putStrLn " q) Powrot do glownego menu (wszystkie zmiany zostana utracone)"
		chose <- getLine
		submenu2 chose tablica

isAddress s = (isAlpha a) && (isInteger i)
		where
			a = head s
			i = tail s

submenu2 x tablica
	| x=="a"	= do
				saveToFile tablica
				actions tablica
	| x=="b"	= do
				putStrLn (showTable tablica)
				actions tablica
	| x=="c"	= do
				putStrLn "Podaj adres komorki do wyswietlenia:"
				cellAddr <- getLine
				if (isAddress cellAddr) 
				then do
					let
						col = findColNumber [(head cellAddr)]
						row = (read (tail cellAddr) :: Int) - 1
					if (( col >= (length (head tablica)) ) || (row >= (length tablica)) || row < 0)
					then 
						putStrLn "Nie mozna wyswietlic nieutworzonej komorki."
					else
						putStrLn (showSingle tablica col row)
				else 
					putStrLn "Nieprawidlowy adres komorki."
				actions tablica
	| x=="d"	= do
				putStrLn "Podaj pozycje nowego wiersza:"
				row <- getLine
				if (isInteger row)
				then do
					let r = (read row :: Int) - 1
					if (r>=0 && r<=(length tablica)) 
					then do
						let w = addRow tablica r
						putStrLn "Wiersz zostal dodany."
						actions w
					else do
						putStrLn "Nieprawidlowy numer wiersza."
						actions tablica
				else do
					putStrLn "Nieprawidlowy numer wiersza."
					actions tablica
	| x=="e"	= do
				putStrLn "Podaj pozycje nowej kolumny:"
				c <- getLine
				if ( ((length c)==1) && ( isAlpha(head c) ) && ( (findColNumber c)<=(length(head tablica))) )
				then do
					let w = addColumn tablica c
					putStrLn "Dodano kolumne."
					actions w
				else do
					putStrLn "Niepoprawna nazwa kolumny."
					actions tablica
	| x=="f"	= do
				putStrLn "Podaj adres komorki do modyfikacji:"
				cellAddr <- getLine
				if (isAddress cellAddr) 
				then do
					let
						col = findColNumber [(head cellAddr)]
						row = (read (tail cellAddr) :: Int) - 1
					if (( col >= (length (head tablica)) ) || (row >= (length tablica)) || row < 0)
					then 
						putStrLn "Nie mozna modyfikowac nieutworzonej komorki."
					else do
						putStrLn "Podaj nową wartość:"
						v <- getLine
						if (isNumeric v) 
						then do
							let 
								val = CellNumber (read v :: Float)
								w = setTableValue tablica val row col
							putStrLn "Wprowadzono wartosc liczbowa."
							actions w
						else do
							if ((head v) == '=') 
							then do
								let 
									val = CellFormula v 0.0
									--OBLICZANIE FORMULY TODO
									w = setTableValue tablica val row col
								putStrLn "Wprowadzono formule."
								actions w
							else do
								let 
									val = CellString v
									w = setTableValue tablica val row col
								putStrLn "Wprowadzono tekst."
								actions w
				else do
					putStrLn "Nieprawidlowy adres komorki."
					actions tablica
	| x/="q"	= do
				putStrLn "Nierozpoznana funkcja!!!"
				actions tablica
	|otherwise	= main

